// --------------------------------------------------------------------------------------------------------------------
// <copyright file="VkontakteAudio.cs" company="3po.ru">
//   Alexander Ulitin [support@3po.ru]
// </copyright>
// <summary>
//   The vkontakte audio.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace Conusic
{
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Net;
    using System.Text;
    using System.Text.RegularExpressions;
    using System.Threading;
    using System.Web;

    using Conusic.Properties;

    /// <summary>
    /// The vkontakte audio.
    /// </summary>
    public static class VkontakteAudio
    {
        #region Constants

        /// <summary>
        /// The artis h_ i n_ regexpr.
        /// </summary>
        private const int ARTISH_IN_REGEXPR = 6;

        /// <summary>
        /// The compositio n_ nam e_ i n_ regexpr.
        /// </summary>
        private const int COMPOSITION_NAME_IN_REGEXPR = 9;

        /// <summary>
        /// The duratio n_ i n_ regexpr.
        /// </summary>
        private const int DURATION_IN_REGEXPR = 5;

        /// <summary>
        /// The las t_ ur l_ par t_ i n_ regexpr.
        /// </summary>
        private const int LAST_URL_PART_IN_REGEXPR = 4;

        /// <summary>
        /// The lyri c_ i d_ i n_ regexpr.
        /// </summary>
        private const int LYRIC_ID_IN_REGEXPR = 8;

        /// <summary>
        /// The serve r_ i d_ i n_ regexpr.
        /// </summary>
        private const int SERVER_ID_IN_REGEXPR = 2;

        /// <summary>
        /// The use r_ i d_ i n_ regexpr.
        /// </summary>
        private const int USER_ID_IN_REGEXPR = 3;

        /// <summary>
        /// The audio regex.
        /// </summary>
        private const string audioRegex =
            @"<div class=""audioRow"" id=""audio[0-9_]*\"".{0,256}"
            + @"operate\(([0-9]*),([0-9]*),([0-9]*),'([a-z0-9]*)',([0-9]*)\).{0,256}?"
            + @"<b id=""performer[0-9]*"">([^<]*)<.{0,64}id=""title[0-9]*"">"
            + @"(.{0,25}showLyrics\([0-9]*,([0-9]*)\);'>)?([^<]*)";

        #endregion

        #region Public Methods and Operators

        /// <summary>
        /// The audio list to json.
        /// </summary>
        /// <param name="list">
        /// The list.
        /// </param>
        /// <returns>
        /// The <see cref="string"/>.
        /// </returns>
        public static string AudioListToJson(IEnumerable<AudioRecord> list)
        {
            if (list == null)
            {
                return "[]";
            }

            var result = new StringBuilder();
            result.AppendLine("[");
            foreach (var audioRecord in list)
            {
                result.AppendLine(string.Format("{0},", audioRecord.Json()));
            }

            result.Remove(result.Length - 1 - Environment.NewLine.Length, 1);
            result.AppendLine("]");
            return result.ToString();
        }

        /// <summary>
        /// request bitrate for given record in currend thread
        /// </summary>
        /// <param name="record">
        /// The record.
        /// </param>
        /// <returns>
        /// The <see cref="int"/>.
        /// </returns>
        public static int GetAverageBitrate(AudioRecord record)
        {
            Config.Instance.BitratePool.WaitOne();

            int averageBitrate = 0;
            try
            {
                var request = WebRequest.Create(record.Url);
                request.Method = "HEAD";
                request.Timeout = 5000;
                var We = request.GetResponse() as HttpWebResponse;
                int size = int.Parse(We.Headers.Get("Content-Length"));

                averageBitrate = Convert.ToInt32(size * 8 / 1024.0 / record.Duration);
            }
            catch
            {
            }

            Config.Instance.BitratePool.Release();
            return averageBitrate;
        }

        /// <summary>
        /// The get hints.
        /// </summary>
        /// <param name="enteredChars">
        /// The entered chars.
        /// </param>
        /// <returns>
        /// The <see cref="List"/>.
        /// </returns>
        public static List<string> GetHints(string enteredChars)
        {
            var result = new List<string>();

            try
            {
                string page = SimpleHttp.RequestVkontaktePage(
                    "hints.php?act=a_gsearch_hints", 
                    string.Format("al=1&q={0}&section=audio", HttpUtility.UrlEncode(enteredChars)));

                ParseHints(ref page, ref result);
            }
            catch (WebException)
            {
            }

            return result;
        }

        /// <summary>
        /// The get lyrics.
        /// </summary>
        /// <param name="lid">
        /// The lid.
        /// </param>
        /// <returns>
        /// The <see cref="string"/>.
        /// </returns>
        public static string GetLyrics(string lid)
        {
            // Wait for Vkontakte access tube
            Config.Instance.VkontakteAccessTube.Wait();

            var request =
                (HttpWebRequest)
                WebRequest.Create(
                    string.Format("{0}audio.php?act=getLyrics&lid={1}", Settings.Default.VkontakteAddress, lid));

            // parametrize proxy and authorization(cookie)
            Config.Instance.SetProxyConfiguration(request);
            Config.Instance.SetVkontakteCookieForWebRequest(request);

            var response = request.GetResponse();

            var datastream = response.GetResponseStream();
            var reader = new StreamReader(datastream, Encoding.GetEncoding(1251));

            string page = reader.ReadToEnd();

            reader.Close();
            datastream.Close();
            response.Close();

            page = page.Replace("<br>", "\n");

            return page;
        }

        /// <summary>
        /// The search audio.
        /// </summary>
        /// <param name="stringToSearch">
        /// The string to search.
        /// </param>
        /// <param name="lyrics">
        /// The lyrics.
        /// </param>
        /// <returns>
        /// The <see cref="List"/>.
        /// </returns>
        public static List<AudioRecord> SearchAudio(string stringToSearch, bool lyrics = false)
        {
            return SearchAudio(null, stringToSearch, lyrics);
        }

        /// <summary>
        /// The search audio async.
        /// </summary>
        /// <param name="command">
        /// The command.
        /// </param>
        /// <param name="stringToSearch">
        /// The string to search.
        /// </param>
        /// <param name="lyrics">
        /// The lyrics.
        /// </param>
        public static void SearchAudioAsync(IAudioSearchCommand command, string stringToSearch, bool lyrics = false)
        {
            ThreadStart threadStart = delegate { SearchAudio(command, stringToSearch, lyrics); };
            var thread = new Thread(threadStart);
            thread.Start();
        }

        /// <summary>
        /// The search one audio.
        /// </summary>
        /// <param name="searchArtistName">
        /// The search artist name.
        /// </param>
        /// <param name="preferedBitrate">
        /// The prefered bitrate.
        /// </param>
        /// <param name="audioRecordCountForBitrateFind">
        /// The audio record count for bitrate find.
        /// </param>
        /// <returns>
        /// The <see cref="AudioRecord"/>.
        /// </returns>
        public static AudioRecord SearchOneAudio(
            string searchArtistName, int preferedBitrate = 270, int audioRecordCountForBitrateFind = 4)
        {
            return SearchOneAudio(null, searchArtistName, preferedBitrate, audioRecordCountForBitrateFind);
        }

        /// <summary>
        /// The search one audio async.
        /// </summary>
        /// <param name="command">
        /// The command.
        /// </param>
        /// <param name="searchArtistSpaceName">
        /// The search artist space name.
        /// </param>
        public static void SearchOneAudioAsync(IAudioOneSearchCommand command, string searchArtistSpaceName)
        {
            ThreadStart threadStart = delegate { SearchOneAudio(command, searchArtistSpaceName); };
            var thread = new Thread(threadStart);
            thread.Start();
        }

        #endregion

        #region Methods

        /// <summary>
        /// The parse audio page.
        /// </summary>
        /// <param name="page">
        /// The page.
        /// </param>
        /// <returns>
        /// The <see cref="List"/>.
        /// </returns>
        private static List<AudioRecord> ParseAudioPage(string page)
        {
            var listAudio = new List<AudioRecord>();
            var regexpr = new Regex(audioRegex, RegexOptions.Singleline);
            Match match = regexpr.Match(page);

            while (match.Success)
            {
                var record = new AudioRecord();

                // int id = int.Parse (match.Groups[1].ToString ());
                string url = "http://cs" + match.Groups[SERVER_ID_IN_REGEXPR] + ".vkontakte.ru/u"
                             + match.Groups[USER_ID_IN_REGEXPR] + "/audio/" + match.Groups[LAST_URL_PART_IN_REGEXPR]
                             + ".mp3";
                record.CompositionName = match.Groups[COMPOSITION_NAME_IN_REGEXPR].ToString();
                record.Artist = match.Groups[ARTISH_IN_REGEXPR].ToString();
                record.Url = url;
                record.Duration = int.Parse(match.Groups[DURATION_IN_REGEXPR].ToString());
                if (match.Groups[LYRIC_ID_IN_REGEXPR].ToString() != string.Empty)
                {
                    record.VkontakteLyricId = int.Parse(match.Groups[LYRIC_ID_IN_REGEXPR].ToString());
                }

                listAudio.Add(record);

                match = match.NextMatch();
            }

            return listAudio;
        }

        /// <summary>
        /// The parse hints.
        /// </summary>
        /// <param name="page">
        /// The page.
        /// </param>
        /// <param name="list">
        /// The list.
        /// </param>
        private static void ParseHints(ref string page, ref List<string> list)
        {
            var regex = new Regex(@"\[[^,]*,[^,]*,[^,]*,""([^""]*)""");
            foreach (Match match in regex.Matches(page))
            {
                list.Add(match.Groups[1].ToString());
            }
        }

        /// <summary>
        /// The search audio.
        /// </summary>
        /// <param name="command">
        /// The command.
        /// </param>
        /// <param name="stringToSearch">
        /// The string to search.
        /// </param>
        /// <param name="lyrics">
        /// The lyrics.
        /// </param>
        /// <returns>
        /// The <see cref="List"/>.
        /// </returns>
        private static List<AudioRecord> SearchAudio(
            IAudioSearchCommand command, string stringToSearch, bool lyrics = false)
        {
            // Wait for Vkontakte access tube
            Config.Instance.VkontakteAccessTube.Wait();

            List<AudioRecord> listAudio = null;

            try
            {
                var urlEndodedSearch = HttpUtility.UrlEncode(stringToSearch);

                string parameterString = string.Format("q={0}&lyrics={1}&count=200", urlEndodedSearch, lyrics ? 1 : 0);
                var response = VkontakteApi.RequestApiList<VkApi.AudioSearch.response>(parameterString);

                listAudio = new List<AudioRecord>(response.count);
                foreach (var responseAudio in response.audio)
                {
                    listAudio.Add(new AudioRecord(responseAudio.artist, responseAudio.title, responseAudio.duration,responseAudio.url));
                }
            }
            catch (Exception e)
            {
                // todo fix bug expression
                Console.WriteLine(e.ToString());
            }

            if (command != null)
            {
                command.Execute(listAudio);
            }

            return listAudio;
        }

        /// <summary>
        /// The search one audio.
        /// </summary>
        /// <param name="command">
        /// The command.
        /// </param>
        /// <param name="searchArtistName">
        /// The search artist name.
        /// </param>
        /// <param name="preferedBitrate">
        /// The prefered bitrate.
        /// </param>
        /// <param name="audioRecordCountForBitrateFind">
        /// The audio record count for bitrate find.
        /// </param>
        /// <returns>
        /// The <see cref="AudioRecord"/>.
        /// </returns>
        private static AudioRecord SearchOneAudio(
            IAudioOneSearchCommand command, 
            string searchArtistName, 
            int preferedBitrate = 270, 
            int audioRecordCountForBitrateFind = 4)
        {
            var list = SearchAudio(searchArtistName);
            return SongChooser.OptimizeFindRecordInList(
                list, searchArtistName, preferedBitrate, audioRecordCountForBitrateFind);
        }

        #endregion
    }
}